/**

Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     licenses@blazegraph.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
/*
 * Created on Aug 20, 2010
 */

package com.bigdata.relation.rule.eval;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Properties;
import java.util.WeakHashMap;

import org.apache.log4j.Logger;

import com.bigdata.bop.IBindingSet;
import com.bigdata.bop.joinGraph.IEvaluationPlan;
import com.bigdata.bop.joinGraph.IEvaluationPlanFactory;
import com.bigdata.journal.IIndexManager;
import com.bigdata.relation.IMutableRelation;
import com.bigdata.relation.accesspath.IBuffer;
import com.bigdata.relation.accesspath.IElementFilter;
import com.bigdata.relation.rule.IRule;

/**
 * Base implementation for {@link IJoinNexusFactory}.
 * 
 * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
 * @version $Id$
 */
abstract public class AbstractJoinNexusFactory implements IJoinNexusFactory {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    private static final transient Logger log = Logger.getLogger(AbstractJoinNexusFactory.class);

    private final ActionEnum action;
    private final long writeTimestamp;
    private /*final*/ long readTimestamp;
    private final Properties properties;
    private final int solutionFlags;
    // @todo should be generic as IElementFilter<ISolution<?>>
    private final IElementFilter<?> solutionFilter;
    private final IEvaluationPlanFactory evaluationPlanFactory;
    private final IRuleTaskFactory defaultRuleTaskFactory;

    public String toString() {

        final StringBuilder sb = new StringBuilder();

        sb.append(getClass().getSimpleName());

//        sb.append("{ ruleContext=" + ruleContext);

        sb.append("{ action=" + action);

        sb.append(", writeTime=" + writeTimestamp);

        sb.append(", readTime=" + readTimestamp);

        sb.append(", properties=" + properties);
        
        sb.append(", solutionFlags=" + solutionFlags);

        sb.append(", solutionFilter="
                + (solutionFilter == null ? "N/A" : solutionFilter.getClass().getName()));

        sb.append(", planFactory=" + evaluationPlanFactory.getClass().getName());

        sb.append(", defaultRuleTaskFactory="
                + defaultRuleTaskFactory.getClass().getName());

        // allow extension by derived classes.
        toString(sb);
        
        sb.append("}");

        return sb.toString();

    }

    /**
     * Allows extension of {@link #toString()} by derived classes.
     * 
     * @param sb
     */
    protected void toString(final StringBuilder sb) {
        
    }

    /**
     * 
     * @param action
     *            Indicates whether this is a Query, Insert, or Delete
     *            operation.
     * @param writeTimestamp
     *            The timestamp of the relation view(s) using to write on the
     *            {@link IMutableRelation}s (ignored if you are not execution
     *            mutation programs).
     * @param readTimestamp
     *            The timestamp of the relation view(s) used to read from the
     *            access paths.
     * @param solutionFlags
     *            Flags controlling the behavior of
     *            {@link #newSolution(IRule, IBindingSet)}.
     * @param solutionFilter
     *            An optional filter that will be applied to keep matching
     *            elements out of the {@link IBuffer} for Query or Mutation
     *            operations.
     * @param evaluationPlanFactory
     *            The factory used to generate {@link IEvaluationPlan}s for
     *            {@link IRule}s.
     * @param defaultRuleTaskFactory
     *            The factory that will be used to generate the
     *            {@link IStepTask} to execute an {@link IRule} unless the
     *            {@link IRule} explicitly specifies a factory object using
     *            {@link IRule#getTaskFactory()}.
     */
    protected AbstractJoinNexusFactory(//
            final ActionEnum action,//
            final long writeTimestamp,//
            final long readTimestamp,//
            final Properties properties,//
            final int solutionFlags, //
            final IElementFilter<?> solutionFilter,//
            final IEvaluationPlanFactory evaluationPlanFactory,//
            final IRuleTaskFactory defaultRuleTaskFactory
            ) {

        if (action == null)
            throw new IllegalArgumentException();

        if (evaluationPlanFactory == null)
            throw new IllegalArgumentException();

        this.action = action;
        
        this.writeTimestamp = writeTimestamp;

        this.readTimestamp = readTimestamp;

        this.properties = properties;
        
        this.solutionFlags = solutionFlags;

        this.solutionFilter = solutionFilter;
        
        this.evaluationPlanFactory = evaluationPlanFactory;
        
        this.defaultRuleTaskFactory = defaultRuleTaskFactory;

        joinNexusCache = new WeakHashMap<IIndexManager, WeakReference<IJoinNexus>>();

    }
    
    // @todo assumes one "central" relation (e.g., SPORelation)?
    public IJoinNexus newInstance(final IIndexManager indexManager) {

        if (indexManager == null)
            throw new IllegalArgumentException();
        
        synchronized (joinNexusCache) {

            final WeakReference<IJoinNexus> ref = joinNexusCache
                    .get(indexManager);

            IJoinNexus joinNexus = ref == null ? null : ref.get();

            if (joinNexus == null) {

                joinNexus = newJoinNexus(indexManager);

                joinNexusCache.put(indexManager, new WeakReference<IJoinNexus>(
                        joinNexus));

            }

            return joinNexus;

        }
        
    }

    /**
     * Factory for {@link IJoinNexus} instances used by
     * {@link #newInstance(IIndexManager)} as past of its singleton pattern.
     */
    abstract protected IJoinNexus newJoinNexus(IIndexManager indexManager);

    private transient WeakHashMap<IIndexManager, WeakReference<IJoinNexus>> joinNexusCache;

    final public ActionEnum getAction() {
        
        return action;
        
    }
    
    final public Properties getProperties() {
        
        return properties;
        
    }
    
    final public long getReadTimestamp() {
        
        return readTimestamp;
        
    }

    final public void setReadTimestamp(final long readTimestamp) {

        if (this.readTimestamp == readTimestamp) {

            // NOP.
            return;

        }
        
        synchronized(joinNexusCache) {
            
            // discard cache since advancing the readTimestamp.
            joinNexusCache.clear();
            
            this.readTimestamp = readTimestamp; 
            
            if(log.isInfoEnabled())
                log.info("readTimestamp: "+readTimestamp);
            
        }
        
    }

    final public long getWriteTimestamp() {
        
        return writeTimestamp;
        
    }

    final public int getSolutionFlags() {
        
        return solutionFlags;
        
    }
    
    final public IElementFilter<?> getSolutionFilter() {
        
        return solutionFilter;
        
    }
    
    final public IEvaluationPlanFactory getEvaluationPlanFactory() {
        
        return evaluationPlanFactory;
        
    }
    
    final public IRuleTaskFactory getDefaultRuleTaskFactory() {
        
        return defaultRuleTaskFactory;
        
    }
    
    private void readObject(java.io.ObjectInputStream in)
         throws IOException, ClassNotFoundException {
        
        /*
         * Note: Must be explicitly allocated when de-serialized.
         */

        joinNexusCache = new WeakHashMap<IIndexManager, WeakReference<IJoinNexus>>();

        in.defaultReadObject();

    }

}
